﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;

namespace FirstFloor.Documents.Pdf
{
    /// <summary>
    /// Represents a PDF dictionary object.
    /// </summary>
    internal class DictionaryObject
        : PdfObject
    {
        private Dictionary<string, object> dictionary;

        /// <summary>
        /// Initializes a new instance of the <see cref="DictionaryObject"/> class.
        /// </summary>
        /// <param name="owner">The owner.</param>
        /// <param name="contents">The contents.</param>
        /// <param name="streamEndPosition">The position in the PDF stream that marks the end of the dictionary.</param>
        public DictionaryObject(PdfParser owner, Dictionary<string, object> contents, long streamEndPosition)
        {
            this.Owner = owner;
            this.dictionary = contents;
            this.StreamEndPosition = streamEndPosition;
        }

        /// <summary>
        /// Gets the PDF parser that owns this dictionary object.
        /// </summary>
        /// <value>The owner.</value>
        public PdfParser Owner { get; private set; }
        /// <summary>
        /// Gets the stream position that marks the end of this dictionary object.
        /// </summary>
        /// <value>The stream end position.</value>
        public long StreamEndPosition { get; private set; }

        /// <summary>
        /// Gets the type of the dictionary.
        /// </summary>
        /// <value>The type.</value>
        public string Type
        {
            get { return (string)this.dictionary["Type"]; }
        }

        /// <summary>
        /// Gets the number of entries contained in this dictionary object.
        /// </summary>
        /// <value>The count.</value>
        public int Count
        {
            get { return this.dictionary.Count; }
        }

        /// <summary>
        /// Gets all keys.
        /// </summary>
        /// <value>All keys.</value>
        public string[] AllKeys
        {
            get
            {
                var keys = new string[this.Count];
                this.dictionary.Keys.CopyTo(keys, 0);
                return keys;
            }
        }

        /// <summary>
        /// Gets the <see cref="System.Object"/> with the specified key.
        /// </summary>
        /// <value></value>
        public object this[string key]
        {
            get { return Get<object>(key); }
            internal set { this.dictionary[key] = value; }
        }

        /// <summary>
        /// Gets the entry having specified key. Raises a KeyNotFoundException if the entry does not exists.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key">The key.</param>
        /// <returns></returns>
        public T Get<T>(string key)
        {
            T value;
            if (!TryGet<T>(key, out value)) {
                throw new KeyNotFoundException(key);
            }
            return value;
        }

        /// <summary>
        /// Gets the entry having specified key. Returns the specified default value if the entry could not be found.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key">The key.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <returns></returns>
        public T Get<T>(string key, T defaultValue)
        {
            T value;
            if (!TryGet<T>(key, out value)) {
                return defaultValue;
            }
            return value;
        }

        /// <summary>
        /// Gets the entry having specified key.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key">The key.</param>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public bool TryGet<T>(string key, out T value)
        {
            object o;
            if (this.dictionary.TryGetValue(key, out o)) {
                if (o is ObjectIdentifier) {
                    // indirect object reference found, read it
                    value = this.Owner.ParseIndirectObject<T>((ObjectIdentifier)o);
                    // replace reference in the dictionary object with the actual value
                    // the next lookup will immediately get the value
                    this.dictionary[key] = o;

                    return true;
                }
                value = ParserUtility.Cast<T>(o);
                return true;
            }
            value = default(T);
            return false;
        }

        /// <summary>
        /// Determines whether this dictionary object contains an entry having specified key.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <returns>
        /// 	<c>true</c> if this dictionary object contains the key; otherwise, <c>false</c>.
        /// </returns>
        public bool ContainsKey(string key)
        {
            return this.dictionary.ContainsKey(key);
        }

#if DEBUG
        /// <summary>
        /// Writes the contents of the dictionary object to debug output.
        /// </summary>
        internal void WriteContents()
        {
            var output = new StringBuilder();
            WriteContents(output, 0);
            Debug.WriteLine(output.ToString());
        }

        /// <summary>
        /// Writes the contents of the dictionary object to the specified output.
        /// </summary>
        /// <param name="output">The output.</param>
        /// <param name="indent">The indent.</param>
        internal void WriteContents(StringBuilder output, int indent)
        {
            foreach (var key in this.AllKeys) {
                // indent
                output.Append(new string(' ', indent * 2));
                output.AppendFormat("{0}: ", key);

                var value = this[key];

                var dict = value as DictionaryObject;
                if (dict != null){
                    output.AppendLine("(DictionaryObject)");
                    dict.WriteContents(output, indent + 1);
                    continue;
                }
                var arr = value as ArrayObject;
                if (arr != null){
                    output.AppendLine("(ArrayObject)");
                    arr.WriteContents(output, indent + 1);
                    continue;
                }
                output.AppendFormat("{0}\r\n", value);
            }
        }
#endif
    }
}
